{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "1ac2e63b",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "#  25\\.  使用 K-Means 完成图像压缩  # \n",
    "\n",
    "##  25.1.  介绍  # \n",
    "\n",
    "本次挑战将针对一张成都著名景点：锦里的图片，通过 Mini Batch K-Means 的方法将相近的像素点聚合后用同一像素点代替，以达到图像压缩的效果。 \n",
    "\n",
    "##  25.2.  知识点  # \n",
    "\n",
    "  * 图像压缩 \n",
    "\n",
    "  * Mini Batch K-Means 聚类 \n",
    "\n",
    "首先，我们下载并导入示例图片，图片名为 ` challenge-7-chengdu.png  ` 。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "65e662bb",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "outputs": [],
   "source": [
    "wget -nc \"https://cdn.aibydoing.com/aibydoing/files/challenge-7-chengdu.png\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a609cc04",
   "metadata": {},
   "outputs": [],
   "source": [
    "--2023-11-13 17:16:21--  https://cdn.aibydoing.com/aibydoing/files/challenge-7-chengdu.png\n",
    "正在解析主机 cdn.aibydoing.com (cdn.aibydoing.com)... 198.18.7.59\n",
    "正在连接 cdn.aibydoing.com (cdn.aibydoing.com)|198.18.7.59|:443... 已连接。\n",
    "已发出 HTTP 请求，正在等待回应... 200 OK\n",
    "长度：1057505 (1.0M) [image/png]\n",
    "正在保存至: “challenge-7-chengdu.png”\n",
    "\n",
    "challenge-7-chengdu 100%[===================>]   1.01M  1.54MB/s  用时 0.7s      \n",
    "\n",
    "2023-11-13 17:16:23 (1.54 MB/s) - 已保存 “challenge-7-chengdu.png” [1057505/1057505])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5e959043",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "使用 Matplotlib 可视化示例图片。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18a31818",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt \n",
    "import matplotlib.image as mpimg \n",
    "%matplotlib inline\n",
    "\n",
    "chengdu = mpimg.imread('challenge-7-chengdu.png') # 将图片加载为 ndarray 数组\n",
    "plt.imshow(chengdu) # 将数组还原成图像"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "76398983",
   "metadata": {},
   "outputs": [],
   "source": [
    "<matplotlib.image.AxesImage at 0x11e371ea0>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d5b3e46",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "[ ![../_images/adf5448f80130079d091f2649f426a56c57fd0a0bc0e56f25f7c6f3876ea7ae7.png](../_images/adf5448f80130079d091f2649f426a56c57fd0a0bc0e56f25f7c6f3876ea7ae7.png) ](../_images/adf5448f80130079d091f2649f426a56c57fd0a0bc0e56f25f7c6f3876ea7ae7.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b009fa46",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "outputs": [],
   "source": [
    "chengdu.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1e46df72",
   "metadata": {},
   "outputs": [],
   "source": [
    "(516, 819, 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d99a86c",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "在使用 ` mpimg.imread  ` 函数读取图片后，实际上返回的是一个 ` numpy.array  ` 类型的数组，该数组表示的是一个像素点的矩阵，包含长，宽，高三个要素。如成都锦里这张图片，总共包含了  $516$  行，  $819$  列共  $516*819=422604$  个像素点，每一个像素点的高度对应着计算机颜色中的三原色 RGB（红，绿，蓝），共 3 个要素构成。 \n",
    "\n",
    "##  25.3.  数据预处理  # \n",
    "\n",
    "为方便后期的数据处理，需要对数据进行降维。 \n",
    "\n",
    "Exercise 25.1 \n",
    "\n",
    "挑战：将形状为  $(516, 819, 3)$  的数据转换为  $(422604, 3)$  形状的数据。 \n",
    "\n",
    "提示：使用 ` np.reshape  ` 进行数据格式的变换。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5a93e6f7",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"数据格式变换\n",
    "\"\"\"\n",
    "## 代码开始 ### (≈ 1 行代码)\n",
    "data = None\n",
    "## 代码结束 ###"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4b46362a",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "参考答案  Exercise 25.1 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0cbf522c",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"数据格式变换\n",
    "\"\"\"\n",
    "### 代码开始 ###(≈ 1 行代码)\n",
    "data = chengdu.reshape(516 * 819, 3)\n",
    "### 代码结束 ###"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "46939831",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "运行测试 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c0ba7e9b",
   "metadata": {},
   "outputs": [],
   "source": [
    "data.shape, data[10]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cbbcfcc2",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "期望输出 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7a411e05",
   "metadata": {},
   "outputs": [],
   "source": [
    "((422604, 3), array([0.12941177, 0.13333334, 0.14901961], dtype=float32))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60b82824",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "##  25.4.  像素点种类个数计算  # \n",
    "\n",
    "尽管有 ` 422604  ` 个像素点，但其中仍然有许多相同的像素点。在此我们定义：RGB 值相同的点为一个种类，其中任意值不同的点为不同种类。 \n",
    "\n",
    "Exercise 25.2 \n",
    "\n",
    "挑战：计算 ` 422604  ` 个像素点中种类的个数。 \n",
    "\n",
    "提示：可以将数据转化为 list 类型，然后将每一个元素转换为 tuple 类型，最后利用 ` set()  ` 和 ` len()  ` 函数进行计算。也可以按照自己的想法完成。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d95466a9",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"计算像素点种类个数\n",
    "\"\"\"\n",
    "def get_variety(data):\n",
    "    \"\"\"\n",
    "    参数:\n",
    "    预处理后像素点集合\n",
    "\n",
    "    返回:\n",
    "    num_variety -- 像素点种类个数\n",
    "    \"\"\"\n",
    "\n",
    "    ### 代码开始 ### (≈ 3 行代码)\n",
    "    num_variety=None\n",
    "    ### 代码结束 ###\n",
    "\n",
    "    return num_variety"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "72a53e08",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "参考答案  Exercise 25.2 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "828fed7b",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"计算像素点种类个数\n",
    "\"\"\"\n",
    "def get_variety(data):\n",
    "    \"\"\"\n",
    "    参数:\n",
    "    预处理后像素点集合\n",
    "\n",
    "    返回:\n",
    "    num_variety -- 像素点种类个数\n",
    "    \"\"\"\n",
    "\n",
    "    ### 代码开始 ### (≈ 3 行代码)\n",
    "    temp=data.tolist()\n",
    "    num_variety=len(set([tuple(t) for t in temp]))\n",
    "    ### 代码结束 ###\n",
    "\n",
    "    return num_variety"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e6f15d44",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "运行测试 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "91c37d9d",
   "metadata": {},
   "outputs": [],
   "source": [
    "get_variety(data), data[20]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c2b77559",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "期望输出 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "45f802c0",
   "metadata": {},
   "outputs": [],
   "source": [
    "(100109, array([0.24705882, 0.23529412, 0.2627451 ], dtype=float32))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a23a4b8c",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "##  25.5.  Mini Batch K-Means 聚类  # \n",
    "\n",
    "像素点种类的数量是决定图片大小的主要因素之一，在此使用 Mini Batch K-Means 的方式将图片的像素点进行聚类，将相似的像素点用同一像素点值来代替，从而降低像素点种类的数量，以达到压缩图片的效果。 \n",
    "\n",
    "Exercise 25.3 \n",
    "\n",
    "挑战：使用 Mini Batch K-Means 聚类方法对像素点进行聚类，并用每一个中心的像素点代替属于该类别的像素点。 \n",
    "\n",
    "规定：聚类簇数量设置为 10 类。 \n",
    "\n",
    "提示：使用 ` MiniBatchKMeans  ` 中 ` fit()  ` 和 ` predict()  ` 函数进行聚类，使用 ` cluster_centers_()  ` 函数进行替换，本次挑战基本使用默认参数。 [ 阅读官方文档 ](http://scikit-learn.org/stable/modules/generated/sklearn.cluster.MiniBatchKMeans.html)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cdee3e4e",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.cluster import MiniBatchKMeans\n",
    "\n",
    "## 代码开始 ### (≈ 4 行代码)\n",
    "predict=None\n",
    "## 代码结束 ###\n",
    "\n",
    "new_colors = model.cluster_centers_[predict]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "221ca3c6",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "参考答案  Exercise 25.3 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "09d4c4ed",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.cluster import MiniBatchKMeans\n",
    "\n",
    "### 代码开始 ###（≈ 4 行代码）\n",
    "model = MiniBatchKMeans(10)\n",
    "model.fit(data)\n",
    "predict=model.predict(data)\n",
    "### 代码结束 ###\n",
    "\n",
    "new_colors = model.cluster_centers_[predict]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2017322c",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "运行测试 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e05fdd10",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 调用前面实现计算像素点种类的函数，计算像素点更新后种类的个数\n",
    "get_variety(new_colors)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f599aeea",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "期望输出 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6b491eae",
   "metadata": {},
   "outputs": [],
   "source": [
    "10"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3256878b",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "##  25.6.  图像压缩前后对比  # \n",
    "\n",
    "Exercise 25.4 \n",
    "\n",
    "挑战：将聚类后并替换为类别中心点值的像素点，变换为数据处理前的格式，并绘制出图片进行对比展示。 \n",
    "\n",
    "提示：使用 ` reshape()  ` 函数进行格式变换，使用 ` imshow()  ` 函数进行绘图。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f16cd02d",
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(1, 2, figsize=(16, 6))\n",
    "\n",
    "## 代码开始 ### (≈ 3 行代码)\n",
    "\n",
    "## 代码结束 ###"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e039ebab",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "参考答案  Exercise 25.4 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ef978e4f",
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(1, 2, figsize=(16, 6))\n",
    "\n",
    "### 代码开始 ### (≈ 3 行代码)\n",
    "new_chengdu = new_colors.reshape(chengdu.shape)\n",
    "ax[0].imshow(chengdu)\n",
    "ax[1].imshow(new_chengdu)\n",
    "### 代码结束 ###"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f144e758",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "期望输出 \n",
    "\n",
    "![image](https://cdn.aibydoing.com/aibydoing/images/document-uid214893labid6102timestamp1531805600911.png)\n",
    "\n",
    "通过图片对比，可以十分容易发现画质被压缩了。其实，因为使用了聚类，压缩后的图片颜色就变为了 10 种。 \n",
    "\n",
    "接下来，使用 ` mpimg.imsave()  ` 函数将压缩好的文件进行存储，并对比压缩前后图像的体积变化。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1897a8de",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 运行对比\n",
    "mpimg.imsave(\"new_chengdu.png\", new_chengdu)\n",
    "!du -h new_chengdu.png\n",
    "!du -h challenge-7-chengdu.png"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d40d37d2",
   "metadata": {},
   "source": [
    "可以看到，使用 Mini Batch K-Means 聚类方法对图像压缩之后，体积明显缩小。 "
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "cell_metadata_filter": "-all",
   "main_language": "python",
   "notebook_metadata_filter": "-all"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
